#pragma once

#include "types.h"

#include <string>
#include <vector>
#include <iostream>

#include <plstream.h>

using namespace std;

class Plot
{
  plstream _plstream;

  FinalResult _results;
  
  string _filename;
  
  inline PLcolor getNextColor();
  
  PLcolor _curColor;
  
  inline map<int,double> buildDataFromCase(const string &caseName, const string &algName);
  
  struct LegendItem
  {
    int32_t opt;
    PLcolor textColor;
    PLcolor lineColor;
    int32_t lineStyle;
    int32_t lineWidth;
    PLcolor symbolColor;
    double symbolScale;
    int32_t symbolNumber;
    string symbol;
    string title;
  };
  
  vector<LegendItem> _legendItems;

public:
  inline Plot(const FinalResult &results, const string &filename);

  inline void addCurveFromData(const map<int,double> &points, PLcolor color, const std::string &c = "");
  
  inline void addCurve(const string &a, const std::string &c);

  inline void buildLegend();
  
  struct Bondaries
  {
    // máximo no eixo Y, horizontal
    double maxY;

    // máximo no eixo X, vertical
    int maxX;
  };
  
  // calcula o intervalo dos eixos do gráfico
  inline Bondaries getBondaries();
};

Plot::Plot(const FinalResult &results, const string& filename):
_results(results),
_filename(filename),
_curColor(Black)
{
  _plstream.sdev("svg");
  _plstream.sfnam(_filename.c_str());
  _plstream.star(1,1);
  _plstream.fontld(1);
  
  _plstream.adv(0);
  
  // qual porção da página será usado?
  //_plstream.vpor(0, 1.0, 0, 1.0);
  
  _plstream.vpor(0.15, 0.85, 0.1, 0.90);
  
  Bondaries b = getBondaries();
  
  // a parte do gráfico renderizada
  _plstream.wind(0,b.maxX,0,b.maxY);
  
  _plstream.col(White);
  
  _plstream.box("bnstv", 0.0, 0,"bnstv", 0.0,0);
  
  _plstream.lab("Tamanho da entrada","Tempo de execucao","Comparacao entre os algoritmos");
}

void Plot::addCurveFromData(const map<int,double> &points, PLcolor color, const string &c)
{
  double *y = new double[points.size()], *x = new double[points.size()];
 
  int i = 0;
  for (auto v: points) {
    y[i] = v.second;
    x[i] = v.first;
    i++;
  }
  
  _plstream.col(color);
  _plstream.line(points.size(), x, y);
  _plstream.string(points.size(), x, y, c.c_str());
  
  delete[] y;
  delete[] x;
}

PLcolor Plot::getNextColor()
{
  int c = _curColor;
  c++;
  return _curColor = (PLcolor)c;
}

void Plot::addCurve(const string& a, const std::string &c)
{
  std::string symbol = "";
  PLcolor color = getNextColor();
  
  LegendItem item {
    PL_LEGEND_LINE | PL_LEGEND_SYMBOL,
    (PLcolor)3,
    color,
    1,
    1,
    (PLcolor)3,
    0.3,
    4,
    symbol,
    a + ": " + c
  };
  
  _legendItems.push_back(item);
  
  addCurveFromData(buildDataFromCase(c,a),color,symbol);  
}

void Plot::buildLegend()
{
  int size = _legendItems.size();
  
  int32_t *opts = new int32_t[size];
  int32_t *textColors = new int32_t[size];
  int32_t *lineColors = new int32_t[size];
  int32_t *lineWidths = new int32_t[size];
  int32_t *lineStyles = new int32_t[size];
  int32_t *symbolNumbers = new int32_t[size];
  int32_t *symbolColors = new int32_t[size];
  double *symbolScales = new double[size];
  const char **symbols = new const char*[size];
  const char **legendText = new const char*[size];
  
  double legendWidth, legendHeight;
  
  for (int i = 0; i < _legendItems.size(); i++) {
    const LegendItem &item = _legendItems[i];
    opts[i] = item.opt;
    textColors[i] = item.textColor;
    lineColors[i] = item.lineColor;
    lineWidths[i] = item.lineWidth;
    lineStyles[i] = item.lineStyle;
    symbolColors[i] = item.symbolColor;
    symbolScales[i] = item.symbolScale;
    symbolNumbers[i] = item.symbolNumber;
    symbols[i] = item.symbol.c_str();
    legendText[i] = item.title.c_str();
  }
 
  // seta a cor de fundo da legenda
  _plstream.scol0a( 15, 32, 32, 32, 0.70 );
  
  _plstream.legend( &legendWidth, &legendHeight,
      PL_LEGEND_BACKGROUND | PL_LEGEND_BOUNDING_BOX, 0,
      0.0, 0.0, 0.10, 15,
      1, 1, 0, 0,
      size, opts,
      1.0, 0.6, 1.0,
      1., textColors, (const char **) legendText,
      NULL, NULL, NULL, NULL,
      lineColors, lineStyles, lineWidths,
      symbolColors, symbolScales, symbolNumbers,
      (const char **) symbols);
  
  delete[] opts;
  delete[] textColors;
  delete[] lineColors;
  delete[] lineWidths;
  delete[] lineStyles;
  delete[] symbolNumbers;
  delete[] symbolColors;
  delete[] symbolScales;
  delete[] symbols;
  delete[] legendText; 
   
}

Plot::Bondaries Plot::getBondaries()
{
  Bondaries b {0.0, 0};
 
  // iterando em cada tamanho de entrada 
  for (auto i: _results) {
    b.maxX = max(b.maxX,i.first);

    // iterando em cada resultado de um algoritmo num caso
    for (auto j: i.second) {
      double m = 0.0;
      
      // obtenho o máximo de cada round
      for (auto k: j.second) {
        m = max(m,k.second);
      }
      
      b.maxY = max(b.maxY,m);
    } 
  }
  
  return b; 
}

map<int,double> Plot::buildDataFromCase(const string &caseName, const string &algName)
{
  map<int,double> r;

  for (auto i: _results) {
    r[i.first] = i.second[caseName][algName];
  }

  return r;
}
